using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Threading;
using System.Net;
using System.Net.Sockets;

namespace Microsoft.Samples.BizTalk.Adapter.Tcp.Receive
{
    /// <summary>
    ///     This worker thread is used to receive data from the duplex client socket and send to the initiator socket.
    ///     On completion it closes the duplex client socket.
    /// </summary>
    /// <author>
    ///     Paul Brown
    /// </author>
    public class DuplexWorkerThread : WorkerThread
    {
        #region Constructor

        /// <summary>
        ///     This is constructor initializes the instance.
        /// </summary>
        public DuplexWorkerThread(IAdapter adapter)
            : base(adapter)
        {
            // Get thread name prefix
            this.ThreadNamePrefix = TraceHelper.ResourceManager.GetString(ResourceConstants.ReceiveDuplexWorkerThreadName);
        }

        #endregion

        #region Abstract WorkerThread Members

        #region Public Instance Properties
        
        /// <summary>
        ///     This property returns the type of a BatchWorkItem which is compatible with this worker thread.
        /// </summary>
        public override Type WorkItemType
        {
            get { return (typeof(DuplexWorkItem)); }
        }

        #endregion

        #region Protected Instance Methods

        /// <summary>
        ///     This method is the delegate that runs the main worker thread.
        /// </summary>
        protected override void Thread()
        {
            // Variables
            bool            threadInterrupted = false;
            DuplexWorkItem  workItem = null;

            try
            {
                // Loop until we are interrupted
                while (true)
                {
                    // Loop while we have work items
                    while (this.CurrentWorkLoad > 0)
                    {
                        // Dequeue item
                        workItem = (DuplexWorkItem)this.DequeueWork();

                        if (this.Adapter.TraceSwitch.TraceVerbose)
                            TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.ReceiveDequeuedDuplexWork, workItem.ReceiveLocation.Uri, this.Name);

                        try
                        {
                            // Receive data from client socket and send to initiator socket
                            long totalBytesSent = SocketIO.ReceiveSendData(this.Adapter, workItem.ClientSocket, workItem.InitiatorSocket, workItem.ReceiveLocation.ReceiveDataBufferSize);

                            // Return total bytes sent as an 8 byte sequence of data in network order
                            workItem.ClientSocket.Send(BitConverter.GetBytes(IPAddress.HostToNetworkOrder(totalBytesSent)));

                            // Close client socket
                            if (workItem.ClientSocket != null)
                            {
                                if (workItem.ClientSocket.Connected)
                                {
                                    workItem.ClientSocket.Shutdown(SocketShutdown.Receive);
                                    workItem.ClientSocket.Close(workItem.ReceiveLocation.ClientCloseTimeout);
                                }
                            }
                        }
                        catch (ThreadInterruptedException)
                        {
                            // Asked to shut down, set flag and wait until this work is done
                            threadInterrupted = true;
                        }
                        catch (Exception e)
                        {
                            // Failed receiving data - error and finish
                            TraceHelper.WriteEntry(TraceLevel.Error, ResourceConstants.ReceiveFailedReceivingData, workItem.ReceiveLocation.Uri, e.Message);

                            // Close client socket
                            if (workItem.ClientSocket != null)
                            {
                                if (workItem.ClientSocket.Connected)
                                {
                                    workItem.ClientSocket.Shutdown(SocketShutdown.Receive);
                                    workItem.ClientSocket.Close(workItem.ReceiveLocation.ClientCloseTimeout);
                                }
                            }
                        }

                        // Update work item
                        workItem.Status = WorkItemStatusTypes.Complete;
                        workItem = null;
                    }

                    // Was the thread interrupted?
                    if (threadInterrupted)
                    {
                        // Throw error again to end thread
                        throw (new ThreadInterruptedException());
                    }

                    if (this.Adapter.TraceSwitch.TraceVerbose)
                        TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.WorkerThreadWaitingForWork, this.Name);

                    // Wait for signal that work has arrived
                    this.WaitForWork();
                }
            }
            catch (ThreadInterruptedException)
            {
                // Asked to shut down
                if (this.Adapter.TraceSwitch.TraceVerbose)
                    TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.WorkerThreadInterrupted, this.Name);

                // If we have any in-flight work still, save it off
                if (workItem != null)
                {
                    workItem.Save();
                }

                while (this.CurrentWorkLoad > 0)
                {
                    workItem = (DuplexWorkItem)this.DequeueWork();
                    workItem.Save();
                }
            }
            catch (Exception e)
            {
                TraceHelper.WriteEntry(TraceLevel.Error, ResourceConstants.WorkerThreadGeneralError, this.Name, e.Message);

                // If we have an outstanding work item, push it back onto queue before restarting
                if (workItem != null)
                {
                    this.PushWorkOnQueue(workItem);
                }
            }
            finally
            {
                // Fire terminate event
                this.OnThreadTerminated(new EventArgs());
            }
        }

        #endregion

        #endregion
    }
}
